package clw;

import clw.toy.*;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;

public class Toy {
    static boolean hadError = false;
    static boolean hadRuntimeError = true;

    private static final Interpreter interpreter = new Interpreter();

    private static String CODE =
        """
                var a = 0;
                var temp ;
                                
                for(var b=1; a < 10000; b=temp+b) {
                   print a;
                   temp = a;
                   a = b;
                }
                
                class Test {
                    init() {
                        print "init at: " + millis();
                    }
                    
                    call(name) {
                        print "hello " + name;
                    }
                }
                
                var test = Test();
                test.call("athena");
                
                var l = List();
                l.add(1);
                l.add(2);
                l.add(3);
                print l;
                
                var map = Map();
                map.put("hello", "kitty");
                map.put("hello", "world");
                map.put("fish", "nemo");
                print map;
                """;

    private static boolean justRun() {
        var code = """
                var l = List();
                l.add("a");
                l.add("b");
                l.add("c");
                for(var i = 0; i < l.size(); i++) {
                    print l.get(i);
                }
                var m = Map();
                m.put(1, "a");
                m.put(2, "b");
                m.put(3, "c");
                for(var i = 1; i<=3;i++){
                    print m.get(i);
                }
                """;
        code = """
                fun fibonacci(n) {
                    if (n<2) {
                        return n;
                    }
                    var p=0;
                    var q=0;
                    var r=1;
                    for(var i=2; i<=n; i++) {
                        p=q;
                        q=r;
                        r=p+q;
                    }
                    return r;
                }
                for(var i=0;i<21;i++) {
                    print fibonacci(i);
                }
                """;
        code = """
                class TreeNode {
                    init(value) {
                        this.value = value;
                        this.left = nil;
                        this.right = nil;
                    }
                }
                var root = TreeNode(1);
                var node2 = TreeNode(2);
                var node3 = TreeNode(3);
                var node4 = TreeNode(4);
                var node5 = TreeNode(5);
                var node6 = TreeNode(6);
                var node7 = TreeNode(7);
                
                root.left = node2;
                root.right= node3;
                node2.left = node4;
                node2.right = node5;
                node3.left = node6;
                node3.right = node7;
                
                fun invertTree(node) {
                    if(node == nil) {
                        return nil;
                    }
                    var tmp = node.left;
                    node.left = node.right;
                    node.right = tmp;
                    invertTree(node.left);
                    invertTree(node.right);
                    return node;
                }
                
                root = invertTree(root);
                print root.value;
                var new_node2 = root.left;
                var new_node3 = root.right;
                var new_node4 = new_node2.left;
                var new_node5 = new_node2.right;
                var new_node6 = new_node3.left;
                var new_node7 = new_node3.right;
                print new_node2.value;
                print new_node3.value;
                print new_node4.value;
                print new_node5.value;
                print new_node6.value;
                print new_node7.value;
                """;
        run(code);
        return true;
    }

    public static void main(String[] args) throws IOException {
        if (justRun()) {
            return;
        }
        if (args.length > 1) {
            System.out.println("Usage: toy [script]");
            // 选项错误退出
            System.exit(64);
        } else if (args.length == 1) {
            runFile(args[0]);
        } else {
            runPrompt();
        }
    }

    private static void runFile(String path) throws IOException {
        byte[] bytes = Files.readAllBytes(Paths.get(path));
        run(new String(bytes, Charset.defaultCharset()));
        if (hadError) {
            System.exit(65);
        }
        if (hadRuntimeError) {
            System.exit(70);
        }
    }

    private static void runPrompt() throws IOException {
        InputStreamReader input = new InputStreamReader(System.in);
        BufferedReader reader = new BufferedReader(input);

        for (; ; ) {
            System.out.print("> ");
            String line = reader.readLine();
            if (line == null) {
                break;
            }
            run(line);
            hadError = false;
        }
    }

    private static void run(String source) {
        Scanner scanner = new Scanner(source);
        List<Token> tokens = scanner.scanTokens();

        Parser parser = new Parser(tokens);
        List<Stmt> statements = parser.parse();
        if (hadError) {
            return;
        }

        Resolver resolver = new Resolver(interpreter);
        resolver.resolve(statements);

        if (hadError) {
            return;
        }
        // System.out.println(new AstPrinter().print(expression));
        interpreter.interpret(statements);
    }

    public static void error(int line, String message) {
        report(line, "", message);
    }

    public static void error(Token token, String message) {
        if (token.type == TokenType.EOF) {
            report(token.line, " at end", message);
        } else {
            report(token.line, " at '" + token.lexeme + "'", message);
        }
    }

    private static void report(int line, String where, String message) {
        System.err.println("[line " + line + "] Error" + where + ": " + message);
        hadError = true;
    }

    public static void runtimeError(RuntimeError error) {
        System.err.println(error.getMessage() + "\n[line " + error.token.line + "]");
        hadRuntimeError = true;
    }
}